﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Text;
using ContainerVariations.Common;
using NUnit.Framework;
using StructureMap;

namespace ContainerVariations.StructureMapSamples
{
    [TestFixture]
    public class StructureMapFixture : TemplateFixture
    {
        [Test]
        public override void Configure_a_single_object_with_container_lifetime()
        {
            var container =
                new Container(x => x.ForRequestedType<IKnob>().TheDefaultIsConcreteType<Knob>().AsSingletons());

            var component01 = container.GetInstance<IKnob>();
            var component02 = container.GetInstance<IKnob>();

            Assert.That(component01, Is.Not.Null);
            Assert.That(component01, Is.TypeOf(typeof(Knob)));
            Assert.That(component01, Is.EqualTo(component02));

        }

        [Test]
        public override void Configure_a_single_object_with_transient_lifetime()
        {
            ObjectFactory.Configure(x => x.ForRequestedType<IKnob>().TheDefaultIsConcreteType<Knob>());

            var component01 = ObjectFactory.GetInstance<IKnob>();
            var component02 = ObjectFactory.GetInstance<IKnob>();

            Assert.That(component01, Is.Not.Null);
            Assert.That(component01, Is.TypeOf(typeof(Knob)));
            Assert.That(component01, Is.Not.EqualTo(component02));
        }

        [Test]
        public override void Configure_a_manually_wired_dependency_graph()
        {
            ObjectFactory.Configure(x =>
                                        {
                                            x.ForRequestedType<IKnob>().TheDefaultIsConcreteType<Knob>();
                                            x.ForRequestedType<IDoor>().TheDefault.Is.OfConcreteType<Door>()
                                                .CtorDependency<IKnob>();
                                            x.ForRequestedType<IHouse>().TheDefault.Is.OfConcreteType<House>()
                                                .CtorDependency<IDoor>();
                                        });

            var house = ObjectFactory.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_an_autowired_dependency_graph()
        {
            var container = new Container(x =>
                                              {
                                                  x.ForRequestedType<IKnob>().TheDefaultIsConcreteType<Knob>();
                                                  x.ForRequestedType<IDoor>().TheDefaultIsConcreteType<Door>();
                                                  x.ForRequestedType<IHouse>().TheDefaultIsConcreteType<House>();
                                              });

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Xml_configure_an_autowired_dependency_graph()
        {
            var house = ObjectFactory.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_an_open_generic()
        {
            var container = new Container(x =>
                                              {
                                                  x.ForRequestedType(typeof(IFlattener<>))
                                                      .TheDefaultIsConcreteType(typeof(PassthruFlattener<>));
                                                  x.ForRequestedType(typeof(IFlattener<Contact>))
                                                      .TheDefaultIsConcreteType(typeof(ContactFlattener));
                                              });

            var contactFlattener = container.GetInstance<IFlattener<Contact>>();
            var addressFlattener = container.GetInstance<IFlattener<Address>>();
            Assert.That(contactFlattener, Is.TypeOf(typeof(ContactFlattener)));
            Assert.That(addressFlattener, Is.TypeOf(typeof(PassthruFlattener<Address>)));
        }

        [Test]
        public override void Configure_a_type_with_a_lambda_or_delegate_dependency()
        {
            string actualValue = null;

            var container = new Container(
                x => x.ForRequestedType<ITestActor>()
                         .TheDefault.Is
                         .ConstructedBy(() => new TestActor(s => actualValue = s)));

            var actor = container.GetInstance<ITestActor>();
            const string expectedValue = "This is the value we're assigning.";
            actor.Act(expectedValue);

            Assert.That(actualValue, Is.EqualTo(expectedValue));
        }

        [Test]
        public override void Configure_all_classes_of_a_specified_type_in_a_specified_assembly()
        {
            var container = new Container(x => 
                                          x.Scan(y => 
                                                     { 
                                                         y.Assembly(typeof(IWidget).Assembly);
                                                         y.AddAllTypesOf<IWidget>();
                                                     })
                );

            IList<IWidget> widgets = container.GetAllInstances<IWidget>();

            foreach (var widget in widgets)
            {
                Console.WriteLine(widget.GetType().Name + ": " + widget.Call());
            }

            Assert.That(widgets.Count, Is.EqualTo(4));
        }

        [Test]
        public override void Configure_a_class_with_multiple_decorators()
        {
            var container = new Container(x =>
            {
                x.ForRequestedType<IDoorUser>()
                    .TheDefault.Is.OfConcreteType<DoorUser>()
                    .WithCtorArg("mayOpenDoor")
                    .EqualTo(true);
                x.ForRequestedType<IKnob>().TheDefaultIsConcreteType<Knob>();
                x.ForRequestedType<IDoor>()
                    .TheDefault.Is.OfConcreteType<Door>()
                    .EnrichWith((context, y) => new EmailDoor(new SecureDoor(y, context.GetInstance<IDoorUser>())));
                x.ForRequestedType<IHouse>().TheDefaultIsConcreteType<House>();
            });

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_a_class_with_scalar_dependencies_from_configuration_api()
        {
            bool mayOpenDoor = Convert.ToBoolean(ConfigurationManager.AppSettings["mayOpenDoor"]);
            var container = new Container(x => x.ForRequestedType<IDoorUser>()
                .TheDefault.Is.OfConcreteType<DoorUser>()
                .WithCtorArg("mayOpenDoor")
                .EqualToAppSetting("mayOpenDoor", "False"));
            var doorUser = container.GetInstance<IDoorUser>();

            Assert.That(Convert.ToBoolean(ConfigurationManager.AppSettings["mayOpenDoor"]), Is.True);
            Assert.That(doorUser.MayOpenDoor, Is.True);
        }

        [Test]
        public override void Container_can_use_an_instance_created_outside_the_container()
        {
            var stringBuilder = new StringBuilder();
            var writer = new StringWriter(stringBuilder);
            var container = new Container(x =>
                {
                    x.ForRequestedType<TextWriter>().TheDefault.IsThis(writer);
                    x.ForRequestedType<ISender>().TheDefaultIsConcreteType<TextWriterSender>();
                });
            var sender = container.GetInstance<ISender>();

            const string message = "This is the message to send.";
            sender.Send(message);
            Assert.That(stringBuilder.ToString(), Is.EqualTo(message));
        }

        [Test]
        [Ignore("The StructureMap container is not IDisposable.")]
        public override void Disposable_classes_with_container_lifetime_are_disposed_when_container_is_disposed()
        {
            /*

               It just doesn't work that way with StructureMap.  Check out the 
               comments regarding IDisposable at this blog entry:
               http://codebetter.com/blogs/jeremy.miller/archive/2009/01/21/interception-capabilities-of-structuremap-2-5.aspx

                    "I'm nearly dead set against the type of IDisposable 
               support that Windsor does.  All I ever hear are complaints 
               of people who have massive memory leaks because Windsor 
               inexplicably keeps refererences."
            
                    "Something like AutoFac's "scoped container" maybe.  How 
               about something like this:

               using (IContainer container = ObjectFactory.NewSession())
               {
                   //do stuff
               } // At the closing of the using statement, all transient objects fetched from container are disposed.

            */
            Assert.Fail();
        }

        [Test]
        [Ignore("The StructureMap container is not IDisposable.")]
        public override void Disposable_classes_with_transient_lifetime_are_disposed_when_container_is_disposed()
        {
            // See comments in method above.
            Assert.Fail();
        }
    }
}